在現代 Angular 中,組件中的雙向資料綁定 (two-way data binding),一般經驗法則是採用單向資料流 (unidirectional data flow) 並實作自訂 event emitter 以將變更傳播到父組件。更流行的解決方案是使用 Subject
或BehaviorSubject
在父組件和子組件之間分享資料。透過 Signal API
和 Model input
,雙向資料綁定 (Two-way data binding) 的複雜性顯著降低。
input
和 event emitter
。 input 名為 x
,event emitter名為 xChange
。Subjec
t 或 BehaviorSubject
來分享資料。model input
將資料雙向綁定 (two-way data binding) 到組件層級的 plain value
或 signal
。今天,我想介紹一下signal
方式的雙向綁定 (two-way data binding),即在組件中使用 model input
。
model.required()
表示組件有指定輸入值。如果組件找不到輸入值,則會拋出錯誤。model()
表示當組件沒有指定輸入值時使用初始值。Model input
允許讀寫操作,而 signal input
是唯讀的。Model input
沒有 transform
屬性;因此,值不能透過變換函數進行變換。Model input
傳回 ModelSignal,而 signal input
傳回 Signal。在這篇文章中,我想展示在組件之間共享資料的舊方法。然後,我將展示 model input
的 signal
方式。 model input
將值綁定到 signal
和 plain variable
, 以在組件之間傳達資料變更。
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from "@angular/core";
import { FormsModule } from "@angular/forms";
@Component({
selector: 'app-sizer',
standalone: true,
imports: [FormsModule],
template: `
<p>Default range slider:</p>
<div>
<input type="range" min="100" max="300"
[ngModel]="size" (ngModelChange)="resize($event)" />
</div>
`,
styles: ``,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppSizerComponent {
@Input() size!: number | string;
@Output() sizeChange = new EventEmitter<number>();
resize(delta: number) {
this.size = delta;
this.sizeChange.emit(this.size);
}
}
AppSizerComponent
組件由一個 slider 組成,該組件將選定的值傳送到父組件。 AppSizerComponent
有一個 size
輸入和一個 sizeChange
event emitter。
@Component({
selector: 'app-root',
standalone: true,
imports: [AppSizerComponent, AppSquareComponent],
template: `
<h3>2-way binding with Input and Event Emitter</h3>
<app-sizer [(size)]="value" />
<app-square [value]="value" />
`,
})
export class App {
value = 120;
}
AppSizerComponent
執行雙向綁定 (two-way data biding),將 value
變數綁定到 size
以獲得最新值。然後,AppSquareComponent
組件會取得輸入值以顯示方塊。
@Component({
selector: 'app-square',
standalone: true,
template: `
<p>Size: {{ value() }}</p>
<div class="square" [style.width.px]="value()" [style.height.px]="value()">{{ value() }}</div>
`,
})
export class AppSquareComponent {
value = input(0);
}
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class AppSizerService {
private valueSub = new BehaviorSubject(120);
value$ = this.valueSub.asObservable();
update(newValue: number) {
this.valueSub.next(newValue);
}
get() {
return this.valueSub.getValue();
}
}
AppSizerService
service 有一個 BehaviorSubject
來儲存 slider 的目前值。 asObservable
將 Observable 分配給 value$
,並且當值變更時訂閱它的其他組件會收到通知。
import { ChangeDetectionStrategy, Component, inject } from "@angular/core";
import { AppSizerService } from "./app-sizer.service";
import { FormsModule } from "@angular/forms";
@Component({
selector: 'app-sizer-subject',
standalone: true,
imports: [FormsModule],
template: `
<p>Default range slider:</p>
<div>
<input type="range" min="100" max="300" [ngModel]="service.get()" (ngModelChange)="service.update($event)">
</div>
`,
})
export class AppSizerSubjectComponent {
service = inject(AppSizerService);
}
此 AppSizerSubjectComponent
組件注入 AppSizerService
service。 NgModel
input 綁定到 service 的 get 方法以顯示目前值。 NgModelChange
event emitter將新值寫入 BehaviorSubject
。
<app-sizer-subject />
<app-square [value]="(value$ | async) ?? 0" />
value$ = inject(AppSizerService).value$
在 App 組件中,它使用 async pipe
來解析 value$
Observable 並將該值傳遞給 AppSquareComponent
input 以顯示正方形。
import { ChangeDetectionStrategy, Component, model } from "@angular/core";
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-sizer-model-input-required',
standalone: true,
imports: [FormsModule],
template: `
<p>Default range slider:</p>
<div>
<input type="range" min="100" max="300" [(ngModel)]="size" />
</div>
`,
})
export class AppSizerModelInputRequiredComponent {
size = model.required({ alias: 'size2' });
}
AppSizerModelInputRequiredComponet
組件具有別名 size2
的 必需的 model input
。輸入欄位的 NgModel
與 size2
model input 雙向綁定 (two-way data binding)。
<h3>Use required model input</h3>
<app-sizer-model-input-required [(size2)]="plain" />
<app-square [value]="plain" />
plain = 120;
組件的 size2
雙向綁定 (two-way data binding) 到 plain
變數。 然後,將 plain
變數傳遞給 AppSquareComponent
以顯示方塊。
<app-sizer-model-input-required [(size2)]="s" />
<app-square [value]="s()" />
s = signal(120);
組件的 size2
與 s
signal 雙向綁定 (two-way data binding)。
<h3>Use model input</h3>
<div>
<input type="range" min="100" max="400" [(ngModel)]="a" />
</div>
<app-square [value]="a()" />
a = model(250);
在此例子中,model input
的初始值為 250。 Range input 將 NgModel
綁定到 model input
並顯示 250。當 range slider 移動時, model input
將設定為新值。 最後, AppSquareComponent
顯示一個具有新長度的方塊。
Two-way data binding
的傳統方式是使用input
和event emitter
。 input
的名稱為 x,event emitter
的名稱為 xChange。subject as a service
。 一個組件向 BehaviorSubject
發出一個值,另一個組件使用Observable
並相應地更新其狀態或HTML 範本。Model input
透過將signal
或plain value
綁定到signal
來簡化two-way data binding
。Model input
允許讀寫操作,而signal input
是唯讀的。與signal input
類似,model input
可以是必需的、可選的並且具有別名。鐵人賽的第 16 天到此結束。